iT邦幫忙

2025 iThome 鐵人賽

DAY 26
0
Mobile Development

我的 Flutter 進化論:30 天打造「Crew Up!」的架構之旅系列 第 26

Day 26 - iOS 部署成功:從 App Store Connect API Keys 到完整自動化流程

  • 分享至 

  • xImage
  •  

大家好,歡迎來到第二十六天!在 Day 25,我們經歷了一場真正的「iOS 部署風暴」:

  • 證書類型錯誤:Apple Push Services 不是 Apple Distribution
  • Apple ID 不匹配:0 valid identities found 讓我們困惑了數小時
  • Provisioning Profile 過期:每解決一個問題,就冒出新的錯誤
  • Fastlane 認證失敗:Missing password 在 CI/CD 環境中無解
  • 版本號衝突:Version code 21 has already been used

那種「每修好一個 bug,又跳出兩個新錯誤」的感覺,相信每個開發者都深有體會。我們在 Keychain Access 和 Apple Developer Portal 之間來回切換,在終端機和 GitHub Actions 日誌中尋找線索,甚至開始懷疑是不是哪個環節的理解出了問題。

但今天,我們終於突破了所有障礙!經過昨天一整天的問題排查和解決,我們成功實現了 Crew Up 專案的完整 iOS 部署自動化。更重要的是,我們找到了從根本上解決這些問題的方式:從傳統的 Apple ID 認證升級到 App Store Connect API Keys,並建立了自動化版本號管理機制。

這些解決方案不僅讓我們的專案能順利部署,更讓我們對 iOS 部署流程有了更深刻的理解。

🎯 問題回顧與最終解決方案

昨天的挑戰總結

在 Day 25 中,我們遇到了以下主要問題:

  1. 證書類型錯誤:使用了 Apple Push Services 而非 Apple Distribution 證書
  2. Apple ID 不匹配:Mac Keychain 中的 Apple ID 與 Developer Portal 不一致
  3. Provisioning Profile 不匹配:更新證書後忘記更新 Profile
  4. Fastlane 認證問題:CI/CD 環境無法處理 2FA 認證
  5. 版本號重複:Google Play Console 上傳時出現版本號衝突

今天的解決方案

經過深入研究和實戰測試,我們成功解決了所有問題,並實現了以下改進:

  1. App Store Connect API Keys 認證:替代傳統的 Apple ID + App Specific Password
  2. 自動版本號管理:解決版本號重複問題
  3. 完整的 CI/CD 流程:從程式碼推送到 TestFlight 上傳的全自動化
  4. 分支策略優化:develop 分支用於測試,main 分支用於生產

💡 核心關鍵(TL;DR)

如果你也遇到類似的 iOS 部署問題,從我們的專案經驗來看,以下是兩個關鍵的解決方式:

1. 改用 App Store Connect API Keys

  • 為什麼:從我們的實作經驗來看,這是解決 CI/CD 中 2FA 認證問題較穩定的方式
  • 效果:不再需要 App-Specific Password,不受雙重認證限制,專為自動化設計
  • 設定:在 App Store Connect 產生 API Key,轉為 Base64 後存入 GitHub Secrets

2. 自動化版本號管理

  • 為什麼:在 CI/CD 流程中加入自動遞增 build number,可以有效避免「版本號已存在」的錯誤
  • 效果:每次推送到 main 分支時,自動遞增版本號並提交回 repository
  • 關鍵:使用 [skip ci] 標記避免觸發無限循環的 CI/CD 流程

這兩個改進讓我們的部署成功率大幅提升。

🔑 App Store Connect API Keys:認證方式的升級

在昨天的實戰中,我們發現傳統的 Apple ID + App Specific Password 認證方式存在以下問題:

傳統方式的缺點

  • ❌ 需要雙重認證 (2FA),在 CI/CD 環境中複雜
  • ❌ App Specific Password 需要定期更新
  • ❌ 認證過程容易出錯,難以除錯
  • ❌ 安全性相對較低,使用主帳戶密碼

API Keys 的優勢

  • ✅ 不需要 2FA,專為自動化設計
  • ✅ 永久有效(除非手動撤銷)
  • ✅ 更安全,可以精確控制權限範圍
  • ✅ 專為 CI/CD 環境優化
  • ✅ 不需要人工介入

App Store Connect API Keys 設定實戰

根據我們在 Day 25 的經驗,以下是正確的設定步驟:

步驟 1:建立 API Key

  1. 前往 App Store Connect

  2. 進入 API Keys 管理

    • 在次級導航欄點擊 Integrations
    • 在左側邊欄選擇 Keys > App Store Connect API
    • 在主要內容區域選擇 Team Keys 標籤
  3. 建立新的 API Key

    • 點擊 "+" 按鈕建立新的 API Key
    • 輸入 Key NameGitHub Actions CI/CD
    • 選擇 Access 權限:
      • 建議選擇 Developer(可以上傳建置、管理 App)
      • 或選擇 App Manager(更多權限)
    • 點擊 Generate
  4. 記錄重要資訊

    • Key ID:例如 ABC1234DEF
    • Issuer ID:頁面上方的 UUID,例如 12345678-1234-1234-1234-123456789abc
    • 下載 .p8 檔案:⚠️ 只能下載一次,請妥善保存

步驟 2:轉換為 Base64 並設定 GitHub Secrets

# 轉換 .p8 檔案為 Base64
base64 -i AuthKey_ABC1234DEF.p8 | pbcopy

# 驗證轉換結果
base64 -i AuthKey_ABC1234DEF.p8 | head -c 100

在 GitHub Repository 中設定以下 3 個 Secrets:

  1. APP_STORE_CONNECT_API_KEY_ID

    • 值:ABC1234DEF(您的 Key ID)
  2. APP_STORE_CONNECT_ISSUER_ID

    • 值:12345678-1234-1234-1234-123456789abc(您的 Issuer ID)
  3. APP_STORE_CONNECT_API_KEY_CONTENT

    • 值:.p8 檔案的 Base64 編碼內容

步驟 3:更新 Fastlane 配置

修改 Fastfile

# ios/fastlane/Fastfile

# 在上傳之前設定 API Key 認證
app_store_connect_api_key(
  key_id: ENV["APP_STORE_CONNECT_API_KEY_ID"],
  issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
  key_content: ENV["APP_STORE_CONNECT_API_KEY_CONTENT"],
  is_key_content_base64: true
)

# 上傳到 TestFlight
upload_to_testflight(
  skip_waiting_for_build_processing: true,
  app_identifier: "com.yourcompany.yourapp"
)

更新 CI/CD 環境變數

# .github/workflows/ci-cd.yml
- name: Build and Deploy iOS to TestFlight
  run: |
    echo "🔧 Environment variables:"
    echo "APP_STORE_CONNECT_API_KEY_ID: $APP_STORE_CONNECT_API_KEY_ID"
    echo "APP_STORE_CONNECT_ISSUER_ID: $APP_STORE_CONNECT_ISSUER_ID"
    echo "APP_STORE_CONNECT_API_KEY_CONTENT is set: ${APP_STORE_CONNECT_API_KEY_CONTENT:+YES}"
    echo ""
    echo "📱 Running Fastlane beta..."
    cd ios
    bundle exec fastlane beta --verbose
  env:
    APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
    APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
    APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}
    APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

🔄 版本號管理:解決重複版本問題

問題分析

在昨天的實戰中,我們遇到了 Google Play Console 上傳失敗的問題:

Error: Version code 21 has already been used.

這個問題的根本原因是:

  • 版本號管理不一致
  • 沒有自動遞增機制
  • CI/CD 沒有在每次部署時自動增加版本號

解決方案:自動版本號管理

1. 版本號格式

在專案中,我們使用語義化版本號格式:

# pubspec.yaml
version: MAJOR.MINOR.PATCH+BUILD_NUMBER
# 範例:version: 1.1.14+29

2. 在 CI/CD 中添加自動版本號遞增

修改 version-management job

# .github/workflows/ci-cd.yml
version-management:
  runs-on: ubuntu-latest
  permissions:
    contents: write
  outputs:
    version: ${{ steps.version.outputs.version }}
    build-number: ${{ steps.version.outputs.build-number }}
    is-release: ${{ steps.version.outputs.is-release }}
  steps:
    - name: Checkout code
      uses: actions/checkout@v5
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Auto increment version for main branch
      if: github.ref == 'refs/heads/main'
      run: |
        # 讀取當前版本
        CURRENT_VERSION=$(grep 'version:' pubspec.yaml | sed 's/version: //' | cut -d'+' -f1)
        CURRENT_BUILD=$(grep 'version:' pubspec.yaml | sed 's/version: //' | cut -d'+' -f2)
        
        # 自動遞增建置編號
        NEW_BUILD=$((CURRENT_BUILD + 1))
        NEW_VERSION_WITH_BUILD="$CURRENT_VERSION+$NEW_BUILD"
        
        # 更新 pubspec.yaml
        sed -i "s/version: .*/version: $NEW_VERSION_WITH_BUILD/" pubspec.yaml
        
        echo "Updated version from $CURRENT_VERSION+$CURRENT_BUILD to $NEW_VERSION_WITH_BUILD"
        
        # 提交版本變更
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git add pubspec.yaml
        # 使用 [skip ci] 避免觸發新的 CI/CD 循環
        git commit -m "chore: auto increment version to $NEW_VERSION_WITH_BUILD [skip ci]" || exit 0
        git push

💡 關於 [skip ci] 的實用說明

在 CI/CD 實作中,[skip ci] 是一個通用慣例,用來告訴 GitHub Actions(以及大多數 CI/CD 系統)不要因為這次由 Action 自己產生的提交而觸發新的 CI/CD 流程

為什麼需要 [skip ci]

  1. 避免無限循環:如果沒有 [skip ci],版本號更新的提交會觸發新的 CI/CD,新的 CI/CD 又會更新版本號,形成無限循環
  2. 節省資源:版本號更新本身不需要重新執行測試和建置
  3. 保持日誌清晰:避免大量的自動提交污染 CI/CD 歷史記錄

其他常見的跳過標記

  • [skip ci] - GitHub Actions、GitLab CI
  • [ci skip] - Travis CI、CircleCI
  • [skip actions] - GitHub Actions 專用
  • ***NO_CI*** - Azure Pipelines

在我們的專案中,使用 [skip ci] 確保了版本號管理流程的穩定性,避免了可能的無限循環問題。

3. 建議讓版本管理在其他 jobs 之前執行

更新依賴關係

# .github/workflows/ci-cd.yml

# 多環境 Android 建置
build-android-multi-env:
  needs: [quality-and-tests, version-management]
  runs-on: ubuntu-latest

# iOS 建置與 TestFlight 部署
build-and-deploy-ios:
  needs: [quality-and-tests, version-management]
  runs-on: macos-latest

版本號管理的實用建議

  1. 語義化版本號:使用 MAJOR.MINOR.PATCH+BUILD 格式
  2. 自動遞增:每次推送到 main 分支自動增加 build number
  3. 唯一性保證:確保每次部署都有唯一的版本號
  4. 可追蹤性:版本變更會自動提交到 Git

🚀 完整的 CI/CD 流程優化

分支策略與部署流程

經過實戰測試,我們確立了以下分支策略:

develop 分支

  • 觸發:推送到 develop 分支
  • 部署:Firebase App Distribution(測試環境)
  • 用途:內部測試和 QA

main 分支

  • 觸發:推送到 main 分支
  • 部署:Google Play Console + TestFlight(生產環境)
  • 用途:正式發布

完整的部署流程

graph TD
    A[開發者推送程式碼] --> B{分支判斷}
    B -->|develop| C[Firebase App Distribution]
    B -->|main| D[版本號自動遞增]
    D --> E[Android 建置]
    D --> F[iOS 建置]
    E --> G[Google Play Console]
    F --> H[TestFlight]
    G --> I[Slack 通知]
    H --> I
    C --> J[測試環境通知]

實際執行結果

在今天的測試中,我們成功實現了:

  1. 版本號自動遞增:自動從當前版本遞增建置編號
  2. iOS TestFlight 上傳成功:使用 App Store Connect API Keys 認證
  3. Google Play Console 部署成功:解決了版本號重複問題
  4. Firebase App Distribution 正確跳過:因為推送到 main 分支

📊 部署結果分析

GitHub Actions 執行結果

從今天的 CI/CD 執行結果可以看到:

成功的 Jobs

  • quality-and-tests:程式碼品質檢查通過
  • version-management:版本號自動遞增成功
  • build-android-multi-env:Android 多環境建置成功
  • build-and-deploy-ios:iOS TestFlight 部署成功
  • deploy-google-play:Google Play Console 部署成功
  • notify-slack:Slack 通知發送成功

正確跳過的 Jobs

  • ⏭️ deploy-firebase-distribution:因為推送到 main 分支而非 develop

關鍵成功指標

  1. 版本號管理

    Updated version from X.Y.Z+N to X.Y.Z+N+1
    # 實際範例:從 1.1.14+29 自動遞增
    
  2. API Key 認證

    🔍 Debug Environment Variables:
    APP_STORE_CONNECT_API_KEY_ID: ABC1234DEF
    APP_STORE_CONNECT_ISSUER_ID: 12345678-1234-1234-1234-123456789abc
    APP_STORE_CONNECT_API_KEY_CONTENT is set: YES
    
  3. 部署成功

    • Google Play Console:新版本成功上傳
    • TestFlight:iOS App 成功上傳

🛠️ 實戰經驗總結

1. App Store Connect API Keys vs Apple ID

傳統方式(已棄用)

env:
  APPLE_ID: ${{ secrets.APPLE_ID }}
  APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}

新的 API Keys 方式(推薦)

env:
  APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
  APP_STORE_CONNECT_ISSUER_ID: ${{ secrets.APP_STORE_CONNECT_ISSUER_ID }}
  APP_STORE_CONNECT_API_KEY_CONTENT: ${{ secrets.APP_STORE_CONNECT_API_KEY_CONTENT }}

2. 版本號管理的重要性

問題:手動管理版本號容易出錯,導致重複版本號
解決:自動化版本號遞增,確保每次部署都有唯一版本號

3. 分支策略的優化

develop 分支:用於測試環境部署
main 分支:用於生產環境部署
好處:清晰的環境分離,避免測試版本影響生產環境

4. CI/CD 權限管理

版本管理 job 需要寫入權限

version-management:
  permissions:
    contents: write

其他 jobs 只需要讀取權限

build-android-multi-env:
  permissions:
    contents: read
    actions: write

🎉 成功案例分享

完整的部署流程

今天我們成功實現了 Crew Up 專案的完整自動化部署:

  1. 程式碼推送 → 觸發 CI/CD
  2. 版本號自動遞增 → 確保唯一性
  3. 多平台建置 → Android + iOS 同時進行
  4. 自動部署 → Google Play + TestFlight
  5. 通知發送 → Slack 通知團隊

關鍵成功因素

  1. 正確的認證方式:App Store Connect API Keys 比傳統方式更穩定
  2. 自動化版本管理:避免手動錯誤,確保版本號唯一性
  3. 清晰的分支策略:測試和生產環境分離
  4. 完整的錯誤處理:每個步驟都有適當的錯誤處理和日誌

開發效率提升

  • 部署時間:從手動 30 分鐘縮短到自動化 15 分鐘
  • 錯誤率:從 70% 降低到接近 0%
  • 團隊合作:自動通知讓整個團隊了解部署狀態
  • 版本追蹤:自動版本號管理讓版本追蹤更清晰

💭 結語

經過兩天的實戰,我們成功解決了 iOS 部署過程中的所有挑戰,並建立了穩定、可靠的自動化部署流程。這個過程讓我們深刻理解了:

技術層面的收穫

  1. App Store Connect API Keys:在我們的專案中,這個方式比傳統 Apple ID 認證更穩定
  2. 版本號管理:自動化版本號遞增能有效避免人為失誤
  3. 分支策略:清晰的環境分離讓部署更安全
  4. CI/CD 權限管理:適當的權限設定能避免很多問題

實戰經驗的價值

  1. 問題解決能力:每個錯誤都是學習的機會
  2. 系統性思考:部署流程需要整體規劃
  3. 持續改進:從錯誤中學習,不斷優化流程
  4. 團隊合作:自動化工具提升團隊效率

下一步計劃

  1. 監控和優化:持續監控部署流程,發現改進機會
  2. 擴展功能:添加更多自動化測試和檢查
  3. 文檔完善:建立完整的部署指南和故障排除手冊
  4. 團隊培訓:確保團隊成員了解新的部署流程

🎉 iOS 部署完成:系列文章總結

經過 Day 23 到 Day 26 的完整實作,我們已經成功建立了從開發到部署的完整自動化流程:

我們完成了什麼?

Day 23 - CI/CD 基礎建立

  • 建立了完整的程式碼品質檢查流程
  • 實作多環境建置策略
  • 設定 Firebase App Distribution
  • 整合 Slack 通知系統

Day 24 - Android 部署實作

  • 解決 Android Keystore 管理問題
  • 實作 ProGuard 程式碼混淆
  • 設定 Google Play Console 自動上傳
  • 處理版本號管理問題

Day 25 - iOS 部署挑戰

  • 遇到證書類型錯誤(Apple Push Services vs Apple Distribution)
  • 解決 Apple ID 不匹配問題
  • 修正 Provisioning Profile 配置
  • 處理 Fastlane 認證困境

Day 26 - iOS 部署成功

  • 升級到 App Store Connect API Keys 認證
  • 實作自動版本號管理機制
  • 完成 TestFlight 自動上傳
  • 建立穩定的部署流程

整體成果

從這四天的實作中,我們建立了:

  • 完整的 CI/CD 流程:從程式碼推送到應用程式上架的全自動化
  • 雙平台支援:Android 和 iOS 同步部署
  • 環境隔離:develop 用於測試,main 用於正式發布
  • 自動化程度高:版本號管理、建置、簽名、上傳全自動
  • 可靠性強:完整的錯誤處理和驗證機制

開發效率提升

  • 部署時間:從手動 30-60 分鐘縮短到自動化 15-20 分鐘
  • 錯誤率:透過自動化大幅降低人為失誤
  • 團隊合作:Slack 通知讓團隊即時掌握部署狀態
  • 版本追蹤:自動版本號管理讓版本追蹤更清晰

下一步

明天(Day 27),我們將分享 Cursor + Claude 實務開發經驗,探討如何在實際專案中運用 AI 輔助開發:

  • Cursor AI 編輯器入門:安裝設定與基本功能介紹
  • AI 輔助的 Clean Architecture 開發:如何讓 AI 理解專案架構規則
  • 實戰案例分享:Crew Up 專案中的 AI 協作經驗與心得

在完成了完整的 CI/CD 自動化部署後,讓我們來看看如何運用 AI 工具來提升開發效率和程式碼品質!

期待與您在 Day 27 相見!


📋 相關資源

📝 專案資訊

  • 專案名稱: Crew Up!
  • 開發日誌: Day 26 - iOS 部署成功:從 App Store Connect API Keys 到完整自動化流程
  • 文章日期: 2025-10-10
  • 技術棧: iOS, Flutter, Fastlane, GitHub Actions, App Store Connect API, TestFlight, Clean Architecture
  • 狀態: ✅ iOS 和 Android 自動化部署流程完成

上一篇
Day 25 - iOS 上架挑戰實戰:從證書錯誤到 TestFlight 部署
下一篇
Day 27 - Cursor + Claude 實戰指南:從 Agent 到 Commit 的完整開發流程
系列文
我的 Flutter 進化論:30 天打造「Crew Up!」的架構之旅30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言